package com.crois.barrier.web.plugin.route;

import com.crois.barrier.common.constants.Constants;
import com.crois.barrier.common.enums.PluginEnum;
import com.crois.barrier.common.enums.PluginTypeEnum;
import com.crois.barrier.common.enums.ResultEnum;
import com.crois.barrier.common.exception.BarrierExceptionEnum;
import com.crois.barrier.plugin.BarrierPlugin;
import com.crois.barrier.plugin.BarrierPluginChain;
import com.crois.barrier.web.resp.BarrierResponseUtils;
import com.crois.barrier.web.resp.BarrierResponseWrapper;
import com.crois.barrier.web.support.ServerWebExchangeUtils;
import io.netty.channel.ConnectTimeoutException;
import org.apache.commons.lang3.StringUtils;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.retry.Backoff;
import reactor.retry.Retry;

import java.time.Duration;
import java.util.Optional;

/**
 * @author Hou Ze Yu
 * @description webClient过滤器
 * @date 2020/7/13
 */
public class WebClientRoutingPlugin implements BarrierPlugin {

    private static  final Logger LOGGER = LoggerFactory.getLogger(WebClientRoutingPlugin.class);

    private final WebClient webClient;

    public WebClientRoutingPlugin(final WebClient webClient) {
        this.webClient = webClient;
    }

    @Override
    public Mono<Void> execute(ServerWebExchange exchange, BarrierPluginChain chain) {
        exchange.getAttributes().put(ServerWebExchangeUtils.BARRIER_HTTP_TYPE, PluginEnum.WEB_CLIENT_ROUTE.getName());
        // 获取请求 url
        String  requestUrl = String.valueOf(exchange.getAttributes().get(ServerWebExchangeUtils.BARRIER_REQUEST_URL_ATTR));

         if (StringUtils.isEmpty(requestUrl)){
              return BarrierResponseUtils.error(exchange, BarrierResponseWrapper.fail(BarrierExceptionEnum.BARRIER_GATEWAY_NOT_FIND_AVAILABLE_URL));
         }
        Long startTime = System.currentTimeMillis();

        exchange.getAttributes().put(ServerWebExchangeUtils.START_TIME, startTime);
        // 获取 timeout
        long timeout = (long) Optional.ofNullable(exchange.getAttribute(ServerWebExchangeUtils.HTTP_TIME_OUT)).orElse(Constants.DEFAULT_BARRIER_HTTP_TIME_OUT);
        LOGGER.info("[webclient request url :{}]",requestUrl);
        HttpMethod httpMethod = HttpMethod.valueOf(exchange.getRequest().getMethodValue());

        WebClient.RequestBodySpec requestBodySpec = webClient.method(httpMethod).uri(requestUrl);
        return handleRequestBody(requestBodySpec,exchange,timeout,chain,requestUrl);
    }
    /**
     * 请求处理
     * @param requestBodySpec
     * @param serverWebExchange
     * @param timeout
     * @param chain
     * @param requestUrl
     * @return
     */
    private  Mono<Void> handleRequestBody(final WebClient.RequestBodySpec requestBodySpec
            , final ServerWebExchange serverWebExchange
            , final long timeout
            , final BarrierPluginChain chain
            ,final String requestUrl){

        Long startTime = (Long) serverWebExchange.getAttributes().get(ServerWebExchangeUtils.START_TIME);
        long retryTimes =  (long) Optional.ofNullable(serverWebExchange.getAttribute(ServerWebExchangeUtils.RETRY_TIMES)).orElse(Constants.DEFAULT_RETRY_TIMES);
        // 开始时间
       // Publisher<Void> publisher =
//        return retryPlugin.apply(serverWebExchange, publisher);
        return requestBodySpec.headers(httpHeaders -> {
            httpHeaders.addAll(serverWebExchange.getRequest().getHeaders());
            httpHeaders.remove(HttpHeaders.HOST);
        })
                .contentType(buildMediaType(serverWebExchange))
                .body(BodyInserters.fromDataBuffers(serverWebExchange.getRequest().getBody()))
                .exchange()
                .doOnError(e -> {
                    LOGGER.error("[webclient request url :{},e:{}]", requestUrl, e.getMessage());
                })
                .timeout(Duration.ofMillis(timeout))
                .retryWhen(Retry.onlyIf(x -> x.exception() instanceof ConnectTimeoutException)
                        .retryMax(retryTimes)
                        .backoff(Backoff.exponential(Duration.ofMillis(200), Duration.ofSeconds(20), 2, true)))
                .flatMap(e -> doNext(e, serverWebExchange, chain,startTime));
    }

    /**
     * 处理返回结果
     * @param clientResponse
     * @param serverWebExchange
     * @param chain
     * @param startTime
     * @return
     */
    private Mono<Void> doNext(final ClientResponse clientResponse
            , final ServerWebExchange serverWebExchange
            , final BarrierPluginChain chain, Long startTime) {

        Long executeTime = (System.currentTimeMillis() - startTime);

        serverWebExchange.getAttributes().put(ServerWebExchangeUtils.BARRIER_REQUEST_EXECUTE_TIME, executeTime);

        String  requestUrl = String.valueOf(serverWebExchange.getAttributes().get(ServerWebExchangeUtils.BARRIER_REQUEST_URL_ATTR));

        if (clientResponse.statusCode().is2xxSuccessful()){

            serverWebExchange.getAttributes().put(ServerWebExchangeUtils.BARRIER_CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.SUCCESS.getName());

        }else {
            serverWebExchange.getAttributes().put(ServerWebExchangeUtils.BARRIER_CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.ERROR.getName());
        }

        LOGGER.info("[webclient request url :{},status:{}]",requestUrl,clientResponse.statusCode());

        serverWebExchange.getAttributes().put(ServerWebExchangeUtils.BARRIER_CLIENT_RESPONSE_ATTR,clientResponse);
        return chain.execute(serverWebExchange);
    }

    /***
     *构建MediaType
     * @param serverWebExchange
     * @return
     */
    private MediaType buildMediaType(ServerWebExchange serverWebExchange) {
        return MediaType.valueOf(Optional.ofNullable(serverWebExchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE)).orElse(MediaType.APPLICATION_JSON_UTF8_VALUE));
    }

    @Override
    public PluginTypeEnum pluginType() {
        return PluginTypeEnum.ROUTE;
    }

    @Override
    public String name() {
        return PluginEnum.WEB_CLIENT_ROUTE.getName();
    }

    @Override
    public int order() {
        return PluginEnum.WEB_CLIENT_ROUTE.getCode();
    }

    @Override
    public Boolean skip(ServerWebExchange exchange) {
        return false;
    }
}
